/*
 * Copyright 2023 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.gradle.buildinit.plugins.internal;

import org.gradle.api.file.Directory;
import org.gradle.internal.UncheckedException;
import org.gradle.util.internal.GFileUtils;
import org.jspecify.annotations.NullMarked;

import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;

import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * Generates version catalogs based on versions, libraries and plugins tracked in the BuildContentGenerationContext's VersionCatalogDependencyRegistry.
 */
@NullMarked
public class VersionCatalogGenerator {

    private final Directory target;

    private VersionCatalogGenerator(Directory target) {
        this.target = target;
    }

    public static VersionCatalogGenerator create(Directory target) {
        return new VersionCatalogGenerator(target);
    }

    public void generate(BuildContentGenerationContext buildContentGenerationContext, boolean withComments) {
        Path gradleDirectory = target.getAsFile().toPath().resolve("gradle");
        GFileUtils.mkdirs(gradleDirectory.toFile());

        try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(gradleDirectory.resolve("libs.versions.toml"), UTF_8))) {
            if (withComments) {
                writer.println("# This file was generated by the Gradle 'init' task.");
                writer.println("# https://docs.gradle.org/current/userguide/version_catalogs.html#sec::toml-dependencies-format");
            }

            boolean needsSeparatorLine = withComments;
            for (Section<?> section : getSections(buildContentGenerationContext)) {
                needsSeparatorLine = section.write(writer, needsSeparatorLine);
            }
        } catch (IOException e) {
            throw UncheckedException.throwAsUncheckedException(e);
        }
    }

    private static List<Section<?>> getSections(BuildContentGenerationContext buildContentGenerationContext) {
        VersionCatalogDependencyRegistry versionCatalogDependencyRegistry = buildContentGenerationContext.getVersionCatalogDependencyRegistry();

        return Arrays.asList(
            new Section<>(
                "[versions]",
                versionCatalogDependencyRegistry.getVersions(),
                v -> format("%s = \"%s\"", v.alias, v.version)
            ),
            new Section<>(
                "[libraries]",
                versionCatalogDependencyRegistry.getLibraries(),
                l -> format("%s = { module = \"%s\", version.ref = \"%s\" }", l.alias, l.module, l.versionRef)
            ),
            new Section<>(
                "[plugins]",
                versionCatalogDependencyRegistry.getPlugins(),
                p -> format("%s = { id = \"%s\", version = \"%s\" }", p.alias, p.pluginId, p.version)
            )
        );
    }

    private static class Section<T> {

        private final String header;
        private final Collection<T> items;
        private final Function<T, String> formatter;

        public Section(String header, Collection<T> items, Function<T, String> formatter) {
            this.header = header;
            this.items = items;
            this.formatter = formatter;
        }

        /**
         * Returns true if anything was written.
         */
        boolean write(PrintWriter writer, boolean needsSeparatorLine) {
            if (items.isEmpty()) {
                return false;
            }

            if (needsSeparatorLine) {
                writer.println();
            }

            writer.println(header);
            for (T item : items) {
                writer.println(formatter.apply(item));
            }

            return true;
        }
    }
}
